#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <nginx.h>
#include "ngx_ev.h"

ngx_uint_t ngx_ev_max_module;

static char* ngx_ev_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static ngx_int_t ngx_ex_add_ports(ngx_conf_t *cf, ngx_array_t *ports, ngx_ev_listen_t *listen);

void ngx_ev_init_connection(ngx_connection_t *c) {

}

static char *
ngx_ex_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);


static ngx_command_t ngx_ev_commands[] = {
    
     {ngx_string("ev"),
     NGX_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS,
     ngx_ev_block,
     0,
     0,
     NULL},

    ngx_null_command
};

static ngx_core_module_t ngx_ev_module_ctx = {
    ngx_string("ev"),
    NULL,
    NULL
};

ngx_module_t ngx_ev_module = {
    NGX_MODULE_V1,
    &ngx_ev_module_ctx, /* module context */
    ngx_ev_commands,    /* module directives */
    NGX_CORE_MODULE,    /* module type */
    NULL,               /* init master */
    NULL,               /* init module */
    NULL,               /* init process */
    NULL,               /* init thread */
    NULL,               /* exit thread */
    NULL,               /* exit process */
    NULL,               /* exit master */
    NGX_MODULE_V1_PADDING
};

static char * 
ngx_ev_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	char                        *rv;
	ngx_uint_t                   m;
	ngx_uint_t                   s;
	ngx_module_t               **modules;
	ngx_ev_conf_ctx_t			*ctx;
	ngx_ev_module_t				*module;
	ngx_uint_t                   mi;
	ngx_conf_t                   pcf;
	ngx_ev_core_srv_conf_t		**cscfp;
	ngx_ev_core_main_conf_t		*cmcf;
	ngx_array_t                  ports;
	ngx_uint_t                   i;
	ngx_ev_listen_t				*listen;


	ctx = ngx_pcalloc(cf->pool, sizeof(ngx_ev_conf_ctx_t));
	if (ctx == NULL) {
		return NGX_CONF_ERROR;
	}

	*(ngx_ev_conf_ctx_t **)conf = ctx;

	/* count the number of the ev modules and set up their indices */

	ngx_ev_max_module = 0;
	for (m = 0; ngx_modules[m]; m++) {
		if (ngx_modules[m]->type != NGX_EV_MODULE) {
			continue;
		}
		ngx_modules[m]->ctx_index = ngx_ev_max_module++;
	}


	/* the ev main_conf context, it is the same in the all rtmp contexts */

	ctx->main_conf = ngx_pcalloc(cf->pool,sizeof(void *) * ngx_ev_max_module);
	if (ctx->main_conf == NULL) {
		return NGX_CONF_ERROR;
	}

	/*
	* the ev null srv_conf context, it is used to merge
	* the server{}s' srv_conf's
	*/

	ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_ev_max_module);
	if (ctx->srv_conf == NULL) {
		return NGX_CONF_ERROR;
	}

	/*
	* the ev null app_conf context, it is used to merge
	* the server{}s' app_conf's
	*/

	ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_ev_max_module);
	if (ctx->app_conf == NULL) {
		return NGX_CONF_ERROR;
	}

#if (nginx_version >= 1009011)
	modules = cf->cycle->modules;
#else
	modules = ngx_modules;
#endif


	for (m = 0; modules[m]; m++) {
		if (modules[m]->type != NGX_EV_MODULE) {
			continue;
		}

		module = modules[m]->ctx;
		mi = modules[m]->ctx_index;

		if (module->create_main_conf) {
			ctx->main_conf[mi] = module->create_main_conf(cf);
			if (ctx->main_conf[mi] == NULL) {
				return NGX_CONF_ERROR;
			}
		}

		if (module->create_srv_conf) {
			ctx->srv_conf[mi] = module->create_srv_conf(cf);
			if (ctx->srv_conf[mi] == NULL) {
				return NGX_CONF_ERROR;
			}
		}

		if (module->create_app_conf) {
			ctx->app_conf[mi] = module->create_app_conf(cf);
			if (ctx->app_conf[mi] == NULL) {
				return NGX_CONF_ERROR;
			}
		}
	}

	pcf = *cf;
	cf->ctx = ctx;

	for (m = 0; modules[m]; m++) {
		if (modules[m]->type != NGX_EV_MODULE) {
			continue;
		}

		module = modules[m]->ctx;

		if (module->preconfiguration) {
			if (module->preconfiguration(cf) != NGX_OK) {
				return NGX_CONF_ERROR;
			}
		}
	}

	/* parse inside the ev{} block */

	cf->module_type = NGX_EV_MODULE;
	cf->cmd_type = NGX_EV_MAIN_CONF;
	rv = ngx_conf_parse(cf, NULL);

	if (rv != NGX_CONF_OK) {
		*cf = pcf;
		return rv;
	}
	
	cmcf = ctx->main_conf[ngx_ev_core_module.ctx_index];
	cscfp = cmcf->servers.elts;
	for (m = 0; modules[m]; m++) {
		if (modules[m]->type != NGX_EV_MODULE) {
			continue;
		}

		module = modules[m]->ctx;
		mi = modules[m]->ctx_index;
		/* init rtmp{} main_conf's */
		cf->ctx = ctx;

		if (module->init_main_conf) {
			rv = module->init_main_conf(cf, ctx->main_conf[mi]);
			if (rv != NGX_CONF_OK) {
				*cf = pcf;
				return rv;
			}
		}

		for (s = 0; s < cmcf->servers.nelts; s++) {

			/* merge the server{}s' srv_conf's */

			cf->ctx = cscfp[s]->ctx;

			if (module->merge_srv_conf) {
				rv = module->merge_srv_conf(cf,
					ctx->srv_conf[mi],
					cscfp[s]->ctx->srv_conf[mi]);
				if (rv != NGX_CONF_OK) {
					*cf = pcf;
					return rv;
				}
			}

			if (module->merge_app_conf) {

				/* merge the server{}'s app_conf */

				/*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/

				rv = module->merge_app_conf(cf,
					ctx->app_conf[mi],
					cscfp[s]->ctx->app_conf[mi]);
				if (rv != NGX_CONF_OK) {
					*cf = pcf;
					return rv;
				}

				/* merge the applications{}' app_conf's */
				//cscfp[s]->ctx->srv_conf[ngx_ev_core_module.ctx_index];
			}

		}
	}

	if (ngx_array_init(&ports, cf->temp_pool, 4, 
		sizeof(ngx_ev_conf_port_t))!= NGX_OK)
	{
		return NGX_CONF_ERROR;
	}

	listen = cmcf->listen.elts;

	for (i = 0; i < cmcf->listen.nelts; i++) {
		if (ngx_ex_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
			return NGX_CONF_ERROR;
		}
	}


    return ngx_ex_optimize_servers(cf, &ports);;
}


static ngx_int_t 
ngx_ex_add_ports(ngx_conf_t *cf, ngx_array_t *ports, ngx_ev_listen_t *listen)
{
	in_port_t				p;
	ngx_uint_t				i;
	struct sockaddr			*sa;
	struct sockaddr_in		*sin;
	ngx_ev_conf_port_t		*port;
	ngx_ev_conf_addr_t		*addr;

#if (NGX_HAVE_INET6)
	struct sockaddr_in6   *sin6;
#endif

	sa = (struct sockaddr *) &listen->sockaddr;

	switch (sa->sa_family) {

#if (NGX_HAVE_INET6)
	case AF_INET6:
		sin6 = (struct sockaddr_in6 *) sa;
		p = sin6->sin6_port;
		break;
#endif

	default: /* AF_INET */
		sin = (struct sockaddr_in *) sa;
		p = sin->sin_port;
		break;
	}

	port = ports->elts;
	for (i = 0; i < ports->nelts; i++) {
		if (p == port[i].port && sa->sa_family == port[i].family) {

			/* a port is already in the port list */

			port = &port[i];
			goto found;
		}
	}

	/* add a port to the port list */

	port = ngx_array_push(ports);
	if (port == NULL) {
		return NGX_ERROR;
	}

	port->family = sa->sa_family;
	port->port = p;

	if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
		sizeof(ngx_ev_conf_addr_t))!= NGX_OK)
	{
		return NGX_ERROR;
	}

found:

	addr = ngx_array_push(&port->addrs);
	if (addr == NULL) {
		return NGX_ERROR;
	}

	addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
	addr->socklen = listen->socklen;
	addr->ctx = listen->ctx;
	addr->bind = listen->bind;
	addr->wildcard = listen->wildcard;
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
	addr->ipv6only = listen->ipv6only;
#endif

	return NGX_OK;
}


static char *
ngx_ex_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
{
	ngx_uint_t             	i, p, last;
	ngx_listening_t  		*ls;
	ngx_ev_port_t       	*mport;
	ngx_ev_conf_port_t		*port;
	ngx_ev_conf_addr_t		*addr;

	port = ports->elts;
	for (p = 0; p < ports->nelts; p++) {

		//ngx_sort(port[p].addrs.elts, (size_t)port[p].addrs.nelts,sizeof(ngx_ev_conf_addr_t), ngx_ev_cmp_conf_addrs);

		addr = port[p].addrs.elts;
		last = port[p].addrs.nelts;

		/*
		* if there is the binding to the "*:port" then we need to bind()
		* to the "*:port" only and ignore the other bindings
		*/


		i = 0;

		while (i < last) {


			ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
			if (ls == NULL) {
				return NGX_CONF_ERROR;
			}


			ls->addr_ntop = 1;
			ls->handler = ngx_ev_init_connection;
			ls->pool_size = 4096;

			/* TODO: error_log directive */
			ls->logp = &cf->cycle->new_log;
			ls->log.data = &ls->addr_text;
			ls->log.handler = ngx_accept_log_error;


#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
			ls->ipv6only = addr[i].ipv6only;
#endif

			mport = ngx_palloc(cf->pool, sizeof(ngx_ev_port_t));
			if (mport == NULL) {
				return NGX_CONF_ERROR;
			}

			ls->servers = mport;

			if (i == last - 1) {
				mport->naddrs = last;

			}
			else {
				mport->naddrs = 1;
				i = 0;
			}

			switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
			case AF_INET6:
				//if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) {
				//	return NGX_CONF_ERROR;
				//}
				//break;
#endif
			default: /* AF_INET */
				//if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) {
				//	return NGX_CONF_ERROR;
				//}
				break;
			}

			addr++;
			last--;
		}
	}

	return NGX_CONF_OK;
}
